1mod house_rules;
5mod rustfmt;
6mod unused_deps;
7mod verify_flowey;
8mod workspace;
9
10use crate::Xtask;
11use anyhow::Context;
12use clap::Parser;
13
14#[derive(Parser)]
16#[clap(
17 about = "Run various formatting checks",
18 disable_help_subcommand = true,
19 subcommand_value_name = "PASS",
20 subcommand_help_heading = "PASSES",
21 after_help = r#"NOTES:
22
23 For documentation on how each pass works, see the corresponding pass's help page.
24"#
25)]
26pub struct Fmt {
27 #[clap(long)]
31 fix: bool,
32
33 #[clap(long)]
35 no_parallel: bool,
36
37 #[clap(long)]
39 only_diffed: bool,
40
41 #[clap(long)]
43 pass: Vec<PassName>,
44
45 #[clap(subcommand)]
47 passes: Option<Passes>,
48}
49
50#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
51enum PassName {
52 HouseRules,
53 Rustfmt,
54 UnusedDeps,
55 VerifyWorkspace,
56 VerifyFuzzers,
57 VerifyFlowey,
58}
59
60impl PassName {
61 fn kebab_case(self) -> &'static str {
62 match self {
63 PassName::HouseRules => "house-rules",
64 PassName::Rustfmt => "rustfmt",
65 PassName::UnusedDeps => "unused-deps",
66 PassName::VerifyWorkspace => "verify-workspace",
67 PassName::VerifyFuzzers => "verify-fuzzers",
68 PassName::VerifyFlowey => "verify-flowey",
69 }
70 }
71}
72
73#[derive(clap::Subcommand)]
74enum Passes {
75 HouseRules(house_rules::HouseRules),
76 Rustfmt(rustfmt::Rustfmt),
77 UnusedDeps(unused_deps::UnusedDeps),
78 VerifyWorkspace(workspace::VerifyWorkspace),
79 VerifyFuzzers(crate::tasks::fuzz::VerifyFuzzers),
80 VerifyFlowey(verify_flowey::VerifyFlowey),
81}
82
83impl Xtask for Fmt {
84 fn run(self, ctx: crate::XtaskCtx) -> anyhow::Result<()> {
85 if let Some(pass) = self.passes {
87 if !self.pass.is_empty() {
88 anyhow::bail!("cannot use `--pass` when invoking pass directly")
89 }
90
91 match pass {
92 Passes::UnusedDeps(cmd) => cmd.run(ctx)?,
93 Passes::Rustfmt(cmd) => cmd.run(ctx)?,
94 Passes::HouseRules(cmd) => cmd.run(ctx)?,
95 Passes::VerifyWorkspace(cmd) => cmd.run(ctx)?,
96 Passes::VerifyFuzzers(cmd) => cmd.run(ctx)?,
97 Passes::VerifyFlowey(cmd) => cmd.run(ctx)?,
98 }
99
100 return Ok(());
101 }
102
103 let tasks: Vec<Box<dyn FnOnce() -> anyhow::Result<()> + Send>> = {
105 fn wrapper(
106 ctx: &crate::XtaskCtx,
107 name: &str,
108 func: impl FnOnce(crate::XtaskCtx) -> anyhow::Result<()> + Send + 'static,
109 ) -> Box<dyn FnOnce() -> anyhow::Result<()> + Send> {
110 let ctx = ctx.clone();
111 let name = name.to_string();
112
113 Box::new(move || {
114 let start_time = std::time::Instant::now();
115 log::info!("[checking] {}", name);
116 let res = func(ctx).context(format!("while running {name}"));
117 log::info!(
118 "[complete] {} ({:.2?})",
119 name,
120 std::time::Instant::now() - start_time
121 );
122 res
123 })
124 }
125
126 let fix = self.fix;
127 let only_diffed = self.only_diffed;
128
129 let passes = if !self.pass.is_empty() {
130 let mut passes = self.pass.clone();
131 passes.sort();
132 passes.dedup_by(|a, b| a == b);
133 passes
134 } else {
135 vec![
137 PassName::HouseRules,
138 PassName::Rustfmt,
139 PassName::UnusedDeps,
140 PassName::VerifyWorkspace,
141 PassName::VerifyFuzzers,
142 PassName::VerifyFlowey,
143 ]
144 };
145
146 passes
147 .into_iter()
148 .map(|pass| {
149 let name = pass.kebab_case();
150 match pass {
151 PassName::HouseRules => wrapper(&ctx, name, {
152 move |ctx| {
153 house_rules::HouseRules::all_passes(fix, only_diffed).run(ctx)
154 }
155 }),
156 PassName::Rustfmt => wrapper(&ctx, name, {
157 move |ctx| rustfmt::Rustfmt::new(fix, only_diffed).run(ctx)
158 }),
159 PassName::UnusedDeps => wrapper(&ctx, name, {
160 move |ctx| unused_deps::UnusedDeps { fix }.run(ctx)
161 }),
162 PassName::VerifyWorkspace => wrapper(&ctx, name, {
163 move |ctx| workspace::VerifyWorkspace.run(ctx)
164 }),
165 PassName::VerifyFuzzers => wrapper(&ctx, name, {
166 move |ctx| crate::tasks::fuzz::VerifyFuzzers.run(ctx)
167 }),
168 PassName::VerifyFlowey => wrapper(&ctx, name, {
169 move |ctx| verify_flowey::VerifyFlowey::new(fix).run(ctx)
170 }),
171 }
172 })
173 .collect()
174 };
175
176 let results: Vec<_> = if self.fix || self.no_parallel {
177 tasks.into_iter().map(|f| (f)()).collect()
178 } else {
179 tasks
180 .into_iter()
181 .map(std::thread::spawn)
182 .collect::<Vec<_>>()
183 .into_iter()
184 .map(|j| j.join().unwrap())
185 .collect()
186 };
187
188 for res in results.iter() {
189 if let Err(e) = res {
190 log::error!("{:#}", e);
191 }
192 }
193
194 if results.iter().any(|res| res.is_err()) && !self.fix {
195 log::error!(
196 "run `cargo xtask fmt{}{} --fix`",
197 if self.only_diffed {
198 " --only-diffed"
199 } else {
200 ""
201 },
202 if !self.pass.is_empty() {
203 self.pass
204 .into_iter()
205 .map(|pass| format!(" --pass {}", pass.kebab_case()))
206 .collect::<Vec<_>>()
207 .join("")
208 } else {
209 "".into()
210 }
211 );
212 Err(anyhow::anyhow!("found formatting errors"))
213 } else {
214 Ok(())
215 }
216 }
217}